/**
 * 
 */
package com.dailydev.jira.portal;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

/**
 * {@link DashboardSharingConfiguration}
 * 
 * @author Roman Bosak, roman.bosak@gmail.com
 */
public class DashboardSharingConfiguration implements
		IDashboardSharingConfiguration {

	/**
	 * Path to default configuration file
	 */
	protected static final String DEFAULT_CONFIG_FILE = "dashboardSharing.properties";

	protected static Logger logger = Logger
			.getLogger(DashboardSharingConfiguration.class);

	/**
	 * 
	 */
	private static final String POSTFIX__AUTHOR = ".author";

	/**
	 * 
	 */
	private static final String POSTFIX__DASHBOARD_ID = ".dashboardId";

	/**
	 * 
	 */
	private static final String POSTFIX__GROUP = ".group";

	/**
	 * 
	 */
	private static final String PREFIX__DASHBOARD_SHARE = "dashboard.share.";

	/**
	 * Configuration file
	 */
	protected String configFile;

	/**
	 * Timestamp when config file last modified - used to recognize if it is
	 * neccessary to update configuration from file
	 */
	protected long configFileLastModified = 0;

	/**
	 * Contains mapping from dashboard id to it's author name
	 */
	protected Map<Long, String> dashboardAuthor = new HashMap<Long, String>();

	/**
	 * Contains mapping from group name to set of dashboard ids
	 */
	protected Map<String, Set<Long>> groupDashboards = new HashMap<String, Set<Long>>();

	/**
	 * Pattern to be used during properties parsing
	 */
	protected Pattern propertyPattern = Pattern
			.compile("^dashboard\\.share\\.([^.]+)\\..+$");

	/**
	 * 
	 */
	public DashboardSharingConfiguration() {
		this(DEFAULT_CONFIG_FILE);
	}

	/**
	 * 
	 */
	public DashboardSharingConfiguration(String configFile) {
		super();
		this.configFile = configFile;
		if (logger.isDebugEnabled()) {
			logger.debug("Instance created. Config file: " + configFile);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.dailydev.jira.portal.IDashboardSharingConfiguration#getDashboardAuthor(java.lang.Long)
	 */
	public String getDashboardAuthor(final Long dashboardId) {
		readUpdatedProperties();
		return dashboardAuthor.get(dashboardId);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.dailydev.jira.portal.IDashboardSharingConfiguration#getDashboardGroups()
	 */
	public Set<String> getDashboardGroups() {
		readUpdatedProperties();
		return groupDashboards.keySet();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.dailydev.jira.portal.IDashboardSharingConfiguration#getGroupDashboards(java.lang.String)
	 */
	public Set<Long> getGroupDashboards(final String groupName) {
		readUpdatedProperties();
		return groupDashboards.get(groupName);
	}

	/**
	 * @param configFile
	 *            the configFile to set
	 */
	public void setConfigFile(String configFile) {
		this.configFile = configFile;
	}

	/**
	 * @return
	 */
	protected boolean checkConfigurationUpdated() {
		long lastModified = getConfigFileLastModified();
		if (lastModified != configFileLastModified || lastModified == 0) {
			return true;
		}
		return false;
	}

	/**
	 * Returns {@link File} containing configuration of the dashboard sharing
	 * 
	 * @return {@link File} containing configuration of the dashboard sharing or
	 *         <code>null</code> if file not found or unreachable
	 */
	protected File getConfigFile() {
		URL resource = getClass().getClassLoader().getResource(configFile);
		if (resource != null) {
			try {
				File file = new File(resource.toURI());
				return file;
			} catch (URISyntaxException e) {
				logger.warn("Unable to get config file > " + configFile, e);
			}
		}
		return null;
	}

	/**
	 * Returns time of last update of the
	 * {@link DashboardSharingConfiguration#configFile} in millis.
	 * 
	 * @return time of last update of the
	 *         {@link DashboardSharingConfiguration#configFile} in millis or 0
	 *         if the file is not found or unreachable.
	 */
	protected long getConfigFileLastModified() {
		File file = getConfigFile();
		if (file != null) {
			return file.lastModified();
		}
		return 0;
	}

	/**
	 * Returns ID of dashboard configuration. The ID is third element of the
	 * property name (example property name: dashboard.share.{id}.groupName).
	 * The property should match pattern
	 * {@link DashboardSharingConfiguration#propertyPattern}
	 * 
	 * @param propertyName
	 *            The name of the property to parse ID from
	 * @return Returns ID of dashboard configuration parsed from
	 *         <code>propertyName</code> using regular expression
	 *         {@link DashboardSharingConfiguration#propertyPattern}.
	 */
	protected String getDashboardConfigID(String propertyName) {
		String dashboardConfigId = null;
		Matcher matcher = propertyPattern.matcher(propertyName);
		if (matcher.matches()) {
			dashboardConfigId = matcher.group(1);
		} else {
			throw new IllegalArgumentException(
					"Property should match with pattern> "
							+ propertyPattern.pattern());
		}
		return dashboardConfigId;
	}

	/**
	 * Returns set of all dashboard configuration IDs used in
	 * <code>properties</code>
	 * 
	 * @param properties
	 *            {@link Properties} to get names to parse from
	 * @return set of all dashboard configuration IDs used in
	 *         <code>properties</code>
	 */
	protected Set<String> getDashboardConfigIDs(Properties properties) {
		Set<String> result = new HashSet<String>();

		Enumeration<String> propertyNames = (Enumeration<String>) properties
				.propertyNames();
		for (; propertyNames.hasMoreElements();) {
			String propertyName = propertyNames.nextElement();
			try {
				result.add(getDashboardConfigID(propertyName));
			} catch (IllegalArgumentException e) {
				logger.warn(
						"Unable to get dashboard configuration ID (third element) from property name '"
								+ propertyName, e);
			}
		}

		return result;
	}

	/**
	 * Method readProperties
	 */
	protected void readProperties() {
		groupDashboards.clear();
		dashboardAuthor.clear();

		Properties properties;
		try {
			properties = readPropertyFile();

			Set<String> dasboardConfigIDs = getDashboardConfigIDs(properties);

			for (String dashboardConfigID : dasboardConfigIDs) {
				String dashboardGroupProperty = PREFIX__DASHBOARD_SHARE
						+ dashboardConfigID + POSTFIX__GROUP;
				if (!properties.containsKey(dashboardGroupProperty)) {
					logger
							.warn("Wrong configuration of shared dashboard with ID '"
									+ dashboardConfigID
									+ "': target users group not defined");
					continue;
				}
				String group = (String) properties.get(dashboardGroupProperty);

				String dashboardAuthorProperty = PREFIX__DASHBOARD_SHARE
						+ dashboardConfigID + POSTFIX__AUTHOR;
				if (!properties.containsKey(dashboardAuthorProperty)) {
					logger
							.warn("Wrong configuration of shared dashboard with ID '"
									+ dashboardConfigID
									+ "': author of the dashboard not defined");
					continue;
				}
				String author = (String) properties
						.get(dashboardAuthorProperty);

				String dashboardIdProperty = PREFIX__DASHBOARD_SHARE
						+ dashboardConfigID + POSTFIX__DASHBOARD_ID;
				if (!properties.containsKey(dashboardIdProperty)) {
					logger
							.warn("Wrong configuration of shared dashboard with ID '"
									+ dashboardConfigID
									+ "': dashboard ID not defined");
					continue;
				}
				String propertyValue = properties
						.getProperty(dashboardIdProperty);

				Long dashboardId;
				try {
					dashboardId = Long.valueOf(propertyValue);
				} catch (NumberFormatException e) {
					logger
							.warn("Wrong configuration of shared dashboard with ID '"
									+ dashboardConfigID
									+ "': dashboard ID should be a Long number");
					continue;
				}

				Set<Long> dashboards = groupDashboards.get(group);

				if (dashboards == null) {
					dashboards = new HashSet<Long>();
					groupDashboards.put(group, dashboards);
				}

				dashboards.add(dashboardId);

				dashboardAuthor.put(dashboardId, author);
			}
		} catch (IOException e) {
			logger.error("Unable to read dashboard sharing configuration", e);
		}
	}

	/**
	 * @return
	 * @throws IOException
	 */
	protected Properties readPropertyFile() throws IOException {
		File file = getConfigFile();
		if (file == null) {
			throw new FileNotFoundException("Configuration resource '"
					+ configFile + "' not found");
		}

		InputStream propertiesInputStream = new FileInputStream(file);

		Properties properties = new Properties();
		try {
			properties.load(propertiesInputStream);
		} finally {
			try {
				propertiesInputStream.close();
			} catch (final IOException e) {
				logger.error("Unable to close property file input stream", e);
			}
		}
		return properties;
	}

	/**
	 * 
	 */
	protected synchronized void readUpdatedProperties() {
		if (checkConfigurationUpdated()) {
			long lastModified = this.getConfigFileLastModified();
			if (logger.isDebugEnabled()) {
				logger.debug("Configuration updated (last read: "
						+ configFileLastModified + ", current timestamp: s"
						+ lastModified + ") => reread");
			}
			readProperties();
			this.configFileLastModified = lastModified;
		}
	}
}
